#include "defaults.h"
#include "utils.h"
#include "nurbs.h"
#include "surfacerenderer.h"
#include <boost/algorithm/string.hpp>

using namespace std;

typedef boost::tuples::tuple< Vec3n, Vec3n, Vec2n > Vertex;
typedef boost::multi_array<Vertex, 2> VertexArray;

void SurfaceRenderer::InitSurface(const vector<nfloat>& A, const vector<nfloat>& B) {
	typedef boost::multi_array<Vertex, 2>::index index;
	typedef boost::multi_array<Vertex, 2>::size_type size_type;
	bool outputObj = false;

	Surface* sprimitive = static_cast<Surface*>(primitive);
	size_type n = A.size();
	size_type m = B.size();
	surface.resize(boost::extents[n][m]);

	nfloat param[2];
	DerivativesDim2 SKL(1);

	nfloat max = -numeric_limits<nfloat>::max();
	nfloat min = numeric_limits<nfloat>::max();
	for(index i = 0; i < n; i++) {
		param[0] = A[i];
		for(index j = 0; j < m; j++) {
			param[1] = B[j];

			Vec3n& P = boost::tuples::get<0>(surface[i][j]);
			Vec3n& N = boost::tuples::get<1>(surface[i][j]);
			Vec2n& tex = boost::tuples::get<2>(surface[i][j]);
			
			sprimitive->Derivative(param, &SKL);

			P = SKL.D[0][0];
			N = 1.0 * cross(SKL.D[1][0], SKL.D[0][1]);
			math::normalize(N);

			tex = math::V2n(param[0], param[1]);
			if(P[2] > max)
				max = P[2];
			if(P[2] < min)
				min = P[2];
		}
	}

	if(outputObj) {
		vector<Vec3n> vertex;
		for(index i = 0; i < n; i++) {
			for(index j = 0; j < m; j++) {
				const Vec3n& P = boost::tuples::get<0>(surface[i][j]);
				cout << "v " << P[0] << " " << P[2] << " " << P[1] << endl;
				vertex.push_back(P);
			}
		}
	
		for(index i = 0; i < n; i++) {
			for(index j = 0; j < m; j++) {
				const Vec2n& tex = boost::tuples::get<2>(surface[i][j]);
				cout << "vt " << tex[0] << " " << tex[1] << endl;
			}
		}
	
		for(index i = 0; i < n; i++) {
			for(index j = 0; j < m; j++) {
				const Vec3n& N = boost::tuples::get<1>(surface[i][j]);
				cout << "vn " << N[0] << " " << N[2] << " " << N[1] << endl;
			}
		}
	
		for(index i = 0; i < n - 1; i++) {
			for(index j = 0; j < m - 1; j++) {
				size_t i0 = i * m + j;
				size_t i1 = i * m + j + 1;
				size_t i2 = (i + 1) * m + j + 1;
				size_t i3 = (i + 1) * m + j;
				cout << "f " << i0 << "/" << i0 << "/" << i0 << " " << i1 << "/" << i1 << "/" << i1 << " " << i2 << "/" << i2 << "/" << i2 << " " << i3 << "/" << i3 << "/" << i3 << endl;
				const Vec3n& P0 = vertex[i0]; 
				const Vec3n& P1 = vertex[i1];
				const Vec3n& P2 = vertex[i2];
				const Vec3n& P3 = vertex[i3];
				visualDebugger.AddQuadrilateral(P0, P1, P2, P3, math::V3n(1.0, 1.0, 0.0));
			}
		}
	}
}

SurfaceRenderer::SurfaceRenderer(Surface* primitive, size_t samplesPatch, const Vec2n& urange, const Vec2n& vrange, Vec3n color, bool renderControlPolygon, bool renderIsoCurves)
								: PrimitiveRenderer(primitive),
								  samplesPatch(samplesPatch),
								  list(-1),
								  listWireframe(-1),
								  listControlPolygon(-1) {
	this->urange = urange;
	this->vrange = vrange;
	this->renderIsoCurves = renderIsoCurves;
	this->renderControlPolygon = renderControlPolygon;
	this->color = color;
	
	Update();

	Init();
}

SurfaceRenderer::~SurfaceRenderer() {
	glDeleteLists(list, 1);
}

void SurfaceRenderer::RenderQuadBack(const Vertex& V1, const Vertex& V2, const Vertex& V3, const Vertex& V4, bool ccw, bool flag1, bool flag2) const {
	float sign;
	if(!ccw)
		sign = 1.0;
	else 
		sign = -1.0;
			
	const Vec3n& P1 = boost::tuples::get<0>(V1);
	const Vec3n& P2 = boost::tuples::get<0>(V2);
	const Vec3n& P3 = boost::tuples::get<0>(V3);
	const Vec3n& P4 = boost::tuples::get<0>(V4);
	const Vec3n& N1 = boost::tuples::get<1>(V1);
	const Vec3n& N2 = boost::tuples::get<1>(V2);
	const Vec3n& N3 = boost::tuples::get<1>(V3);
	const Vec3n& N4 = boost::tuples::get<1>(V4);

	Vec2n tex1, tex2, tex3, tex4;
// 	if(renderIsoCurves) {
 	{
		tex1 = boost::tuples::get<2>(V1);
		tex2 = boost::tuples::get<2>(V2);
		tex3 = boost::tuples::get<2>(V3);
		tex4 = boost::tuples::get<2>(V4);
	}

	math::Vec3f p1 = math::V3f(P1[0], P1[2], P1[1]);
	math::Vec3f p2 = math::V3f(P2[0], P2[2], P2[1]);
	math::Vec3f p3 = math::V3f(P3[0], P3[2], P3[1]);
	math::Vec3f p4 = math::V3f(P4[0], P4[2], P4[1]);
	
	math::Vec3f n1 = (1.0f * sign) * math::V3f(N1[0], N1[2], N1[1]);
	math::Vec3f n2 = (1.0f * sign) * math::V3f(N2[0], N2[2], N2[1]);
	math::Vec3f n3 = (1.0f * sign) * math::V3f(N3[0], N3[2], N3[1]);
	math::Vec3f n4 = (1.0f * sign) * math::V3f(N4[0], N4[2], N4[1]);
	normalize(n1);
	normalize(n2);
	normalize(n3);
	normalize(n4);

	glEdgeFlag(GL_FALSE);
	glNormal3f(n3[0], n3[1], n3[2]);
	glTexCoord2d(tex3[0], tex3[1]);
	glVertex3f(p3[0], p3[1], p3[2]);
	
	if(flag1)	
		glEdgeFlag(GL_TRUE);
	else
		glEdgeFlag(GL_FALSE);
	glNormal3f(n2[0], n2[1], n2[2]);
	glTexCoord2d(tex2[0], tex2[1]);
	glVertex3f(p2[0], p2[1], p2[2]);
	
	glEdgeFlag(GL_FALSE);
	glNormal3f(n1[0], n1[1], n1[2]);
	glTexCoord2d(tex1[0], tex1[1]);
	glVertex3f(p1[0], p1[1], p1[2]);
	
	if(flag2)
		glEdgeFlag(GL_TRUE);
	else
		glEdgeFlag(GL_FALSE);
	glNormal3f(n1[0], n1[1], n1[2]);
	glTexCoord2d(tex1[0], tex1[1]);
	glVertex3f(p1[0], p1[1], p1[2]);
	
	glEdgeFlag(GL_FALSE);
	glNormal3f(n4[0], n4[1], n4[2]);
	glTexCoord2d(tex4[0], tex4[1]);
	glVertex3f(p4[0], p4[1], p4[2]);

	glEdgeFlag(GL_FALSE);
	glNormal3f(n3[0], n3[1], n3[2]);
	glTexCoord2d(tex3[0], tex3[1]);
	glVertex3f(p3[0], p3[1], p3[2]);
}

void SurfaceRenderer::RenderSurface(size_t samplesPatch, bool flag) const {
	typedef VertexArray::index index;
	typedef VertexArray::size_type size_type;

	size_type n = surface.shape()[0];
	size_type m = surface.shape()[1];

	for(index i = 0; i < n - 1; i++) {
		for(index j = 0; j < m - 1; j++) {
			if(flag) {
				RenderQuadBack(surface[i][j], surface[i][j + 1], surface[i + 1][j + 1], surface[i + 1][j], true);
			} else {
				bool flag1 = (i % samplesPatch) == 0 ? true : false;
				bool flag2 = (j % samplesPatch) == 0 ? true : false;
				RenderQuadBack(surface[i][j], surface[i][j + 1], surface[i + 1][j + 1], surface[i + 1][j], true, flag1, flag2);
			}
		}
	}
}

void SurfaceRenderer::RenderWireframeGL() const {
	glPushAttrib(GL_POLYGON_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT | GL_LINE_BIT);
	glLineWidth(2.0);
	glDisable(GL_LIGHTING);
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glColor3f(0.0f, 0.0f, 0.0f);
	glBegin(GL_TRIANGLES);
	RenderSurface(samplesPatch, false);
	glEnd();
	glPopAttrib();
}

void SurfaceRenderer::RenderControlpolygonGL() const {
	glPushAttrib(GL_LINE_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT);
	glDisable(GL_LIGHTING);
	glLineWidth(2.0);
	Surface* sprimitive = static_cast<Surface*>(primitive);
	glColor3f(1.0f, 1.0f, 0.0f);
	for(size_t i = 0; i < sprimitive->Size0(); i++) {
		glBegin(GL_LINE_STRIP);
		for(size_t j = 0; j < sprimitive->Size1(); j++) {
			const Vec4n& P = (*sprimitive)(i, j);
			glVertex4f(P[0], P[2], P[1], P[3]);
		}
		glEnd();
		}
	
	for(size_t i = 0; i < sprimitive->Size1(); i++) {
		glBegin(GL_LINE_STRIP);
		for(size_t j = 0; j < sprimitive->Size0(); j++) {
			const Vec4n& P = (*sprimitive)(j, i);
			glVertex4f(P[0], P[2], P[1], P[3]);
		}
		glEnd();
	}
	glPopAttrib();
}

void SurfaceRenderer::RenderGL() const {
	glPushAttrib(GL_POLYGON_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT);

	glPolygonOffset(1.0, 1.0);
	glEnable(GL_POLYGON_OFFSET_FILL);
	glEnable(GL_LIGHTING);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	GLfloat mat_diffuse[4] = {color[0], color[1], color[2], 1.0};
	GLfloat mat_white[4] = {0.7, 0.7, 0.7, 1.0};

	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, mat_diffuse); 
	glBegin(GL_TRIANGLES);
	RenderSurface(samplesPatch);
	glEnd();
	glDisable(GL_POLYGON_OFFSET_FILL);
	glPopAttrib();
}

void SurfaceRenderer::Init() {
	Update();
}

void SurfaceRenderer::Update() {
	Surface* surface = static_cast<Surface*>(primitive);
	size_t pu = surface->GetDegreeU();
	size_t pv = surface->GetDegreeV();
	const Knotvector& U = surface->GetKnotvectorU();
	const Knotvector& V = surface->GetKnotvectorV();

	vector<nfloat> Unew = DetermineParameters(U, surface->Size0() - 1, pu, 0.0, 1.0, samplesPatch);
	vector<nfloat> Vnew = DetermineParameters(V, surface->Size1() - 1, pv, 0.0, 1.0, samplesPatch);
	
	numisocurvesU = Unew.size();
	numisocurvesV = Vnew.size();

	InitSurface(Unew, Vnew);
}

void SurfaceRenderer::Render() const {
	static GLint tempLight = -1;
	static GLint tempTexture = -1;
	glPushAttrib(GL_ENABLE_BIT);

	glEnable(GL_BLEND);
	
	RenderGL();

 	if(renderControlPolygon)
		RenderControlpolygonGL();
	
	if(renderIsoCurves) {
		glCallList(listWireframe);
	 	RenderWireframeGL();
	}
	
	glPopAttrib();

	glDisable(GL_BLEND);
}
